<template>
  <Context ref="viewsContext" class="select" @shown="shown">
    <div class="select__search">
      <i class="select__search-icon fas fa-search"></i>
      <input
        v-model="query"
        type="text"
        class="select__search-input"
        :placeholder="$t('viewsContext.searchView')"
      />
    </div>
    <div v-if="isLoading" class="context--loading">
      <div class="loading"></div>
    </div>
    <ul
      v-if="!isLoading && views.length > 0"
      ref="dropdown"
      v-auto-overflow-scroll
      class="select__items"
    >
      <ViewsContextItem
        v-for="view in searchAndOrder(views)"
        :ref="'view-' + view.id"
        :key="view.id"
        v-sortable="{
          enabled: !readOnly,
          id: view.id,
          update: order,
          marginTop: -1.5,
        }"
        :view="view"
        :table="table"
        :read-only="readOnly"
        @selected="selectedView"
      ></ViewsContextItem>
    </ul>
    <div v-if="!isLoading && views.length == 0" class="context__description">
      {{ $t('viewsContext.noViews') }}
    </div>
    <div v-if="!readOnly" class="select__footer">
      <div class="select__footer-create">
        <CreateViewLink
          v-for="(viewType, type) in viewTypes"
          :key="type"
          :table="table"
          :view-type="viewType"
          @created="selectedView"
        ></CreateViewLink>
      </div>
    </div>
  </Context>
</template>

<script>
import { mapState } from 'vuex'

import { notifyIf } from '@baserow/modules/core/utils/error'
import { escapeRegExp } from '@baserow/modules/core/utils/string'
import context from '@baserow/modules/core/mixins/context'
import dropdownHelpers from '@baserow/modules/core/mixins/dropdownHelpers'
import ViewsContextItem from '@baserow/modules/database/components/view/ViewsContextItem'
import CreateViewLink from '@baserow/modules/database/components/view/CreateViewLink'

export default {
  name: 'ViewsContext',
  components: {
    ViewsContextItem,
    CreateViewLink,
  },
  mixins: [context, dropdownHelpers],
  props: {
    table: {
      type: Object,
      required: true,
    },
    views: {
      type: Array,
      required: true,
    },
    readOnly: {
      type: Boolean,
      required: false,
      default: true,
    },
  },
  data() {
    return {
      query: '',
      firstShow: true,
    }
  },
  computed: {
    viewTypes() {
      return this.$registry.getAll('view')
    },
    ...mapState({
      isLoading: (state) => state.view.loading,
      isLoaded: (state) => state.view.loaded,
    }),
  },
  methods: {
    shown() {
      if (this.firstShow) {
        this.$nextTick(() => {
          this.scrollViewDropdownIfNeeded()
        })
        this.firstShow = false
      }
    },
    selectedView(view) {
      this.hide()
      this.$emit('selected-view', view)
    },
    /*
      If the currently selected view is not visible inside the dropdown we need to
      scroll just enough so that the selected view is visible as the last element
      in the dropdown.
      In case there are no views, we don't need to do anything and can simply return.
    */
    scrollViewDropdownIfNeeded() {
      if (this.views.length === 0) {
        return
      }
      const dropdownElement = this.$refs.dropdown
      const selectedViewItem = this.getSelectedViewItem()
      const dropdownHeight = dropdownElement.clientHeight
      if (
        this.isSelectedViewOutOfDropdownView(selectedViewItem, dropdownHeight)
      ) {
        dropdownElement.scrollTop = this.calculateOffsetToSelectedViewItem(
          dropdownElement,
          selectedViewItem
        )
      }
    },
    /**
     * This method filters the view elements and returns the currently selected
     * view dom item based on whether or not it is selected.
     */
    getSelectedViewItem() {
      const selectedViewArray = this.views.filter((item) => item._.selected)
      const selectedViewItemID = selectedViewArray[0].id
      return this.$refs[`view-${selectedViewItemID}`][0].$el
    },
    /**
     * This method calculates whether or not the selectedViewItem is fully visible
     * inside the ViewContext dropdown or not
     */
    isSelectedViewOutOfDropdownView(selectedViewItem, dropdownHeight) {
      const selectedOffsetPlusHeight =
        selectedViewItem.offsetTop + selectedViewItem.clientHeight
      return selectedOffsetPlusHeight > dropdownHeight
    },
    /**
     * This method calculates the necessary offsetTop of the dropdown element so that
     * the selected view item is the bottom element.
     */
    calculateOffsetToSelectedViewItem(dropdownElement, selectedViewItem) {
      const {
        parentContainerBeforeHeight,
        itemHeightWithMargins,
        itemsInView,
      } = this.getStyleProperties(dropdownElement, selectedViewItem)

      const viewItemsBeforeSelectedViewItemHeight =
        (itemsInView - 1) * itemHeightWithMargins

      return (
        selectedViewItem.offsetTop -
        viewItemsBeforeSelectedViewItemHeight -
        parentContainerBeforeHeight
      )
    },
    searchAndOrder(views) {
      const query = this.query

      return views
        .filter(function (view) {
          const regex = new RegExp('(' + escapeRegExp(query) + ')', 'i')
          return view.name.match(regex)
        })
        .sort((a, b) => a.order - b.order)
    },
    async order(order, oldOrder) {
      try {
        await this.$store.dispatch('view/order', {
          table: this.table,
          order,
          oldOrder,
        })
      } catch (error) {
        notifyIf(error, 'view')
      }
    },
  },
}
</script>
